#include "../include/pool.h"

void Delete_Pool(Pool *pool)
{
    for (int i = 0; i < pool->mblock_length; i++)
    {
        MBlock *mblock = pool->blocks[i];
        while (mblock != NULL)
        {
            MBlock *next = mblock->next;
            free(mblock);
            mblock = next;
        }
    }
}

MBlock *Pool_Create_Block(Pool *self, u_byte index)
{
    u_short size = self->mblock_info[index][0];
    u_short count = self->mblock_info[index][1] * (self->mblock_count[index] + 1);
    MBlock *block = malloc(sizeof(MBlock) + count * (sizeof(u_byte) + size) + (sizeof(void *) * ((_int)(count / MAX_NUMBER_OF_MBLOCK) + 1)));
    block->count = count;
    block->usedCount = 0;
    block->index = index;

    block->previous = NULL;
    block->next = NULL;

    void *memory = (void *)block + sizeof(MBlock);
    block->root = memory + sizeof(void *) + sizeof(u_byte);

    u_int i = 0;
    while (i < count)
    {
        *(void **)memory = block;
        memory += sizeof(void *);
        for (u_int offset = 0; offset < MAX_NUMBER_OF_MBLOCK && i < count; offset++, i++)
        {
            memory += sizeof(u_byte);
            POINTER_SET_INFO(memory, index, offset);
            *(void **)memory = memory + size + sizeof(u_byte);
            memory = memory + size;
        }
        *(void **)(memory - size) = memory + sizeof(void *) + sizeof(u_byte);
    }
    *(void **)(memory - size) = NULL;
    return block;
}

void Pool_Init(Pool *self, u_short info[][2])
{
    for (u_byte i = 0; i < MAX_NUMBER_OF_MBLOCK; i++)
    {
        self->mblock_length = i;
        if (info[i][0] == 0)
        {
            break;
        }
        self->mblock_info[i][0] = info[i][0];
        self->mblock_info[i][1] = info[i][1];
    }
    for (u_int i = 0; i < self->mblock_length; i++)
    {
        self->reserve_blocks[i] = NULL;
        self->blocks[i] = NULL;
        self->mblock_count[i] = 0;
    }
    pthread_mutex_init(&self->mutex, NULL);
}

void *Pool_Malloc(Pool *self, u_int size)
{
    if (size == 0)
    {
        return NULL;
    }
    if (size > self->mblock_info[self->mblock_length - 1][0])
    {
        void *memory = malloc(sizeof(u_byte) + size) + sizeof(u_byte);
        POINTER_SET_INFO(memory, self->mblock_length, 0);
        return memory;
    }
    u_byte index = 0;
    while (self->mblock_info[index][0] < size)
        index++;
    pthread_mutex_lock(&self->mutex);
    MBlock *block = self->blocks[index];
    while (block != NULL)
    {
        if (block->root != NULL)
        {
            break;
        }
        block = block->next;
    }
    if (block == NULL)
    {
        if (self->reserve_blocks[index] != NULL)
        {
            block = self->reserve_blocks[index];
            self->reserve_blocks[index] = NULL;
        }
        else
        {
            block = Pool_Create_Block(self, index);
        }
        block->next = self->blocks[index];
        self->blocks[index] = block;
        if (block->next != NULL)
            block->next->previous = block;
        self->mblock_count[index]++;
    }
    void *result = block->root;

    block->root = *(void **)block->root;
    block->usedCount++;

    pthread_mutex_unlock(&self->mutex);
    return result;
}

void *Pool_Calloc(Pool *self, u_int size)
{
    void *node = Pool_Malloc(self, size);
    memset(node, 0, size);
    return node;
}

void *Pool_Realloc(Pool *self, void *pointer, u_int size)
{
    void *result = NULL;
    if (pointer == NULL)
    {
        return Pool_Malloc(self, size);
    }
    u_byte index = POINTER_GET_INDEX(pointer);
    if (index == self->mblock_length)
    {
        result = realloc(pointer - sizeof(u_byte), sizeof(u_byte) + size);
        result += sizeof(u_byte);
        POINTER_SET_INFO(result, self->mblock_length, 0);
    }
    else if (size < self->mblock_info[index][0])
    {
        result = pointer;
    }
    else
    {
        MBlock *block = POINTER_TO_MBLOCK(self->mblock_info, pointer);

        if (size != 0)
        {
            result = Pool_Malloc(self, size);
            memcpy(result, pointer, self->mblock_info[block->index][0]);
        }
        Pool_Free(self, pointer);
    }
    return result;
}

void Pool_Free(Pool *self, void *pointer)
{
    if (pointer == NULL)
    {
        return;
    }
    if (POINTER_GET_INDEX(pointer) == self->mblock_length)
    {
        free(pointer - sizeof(u_byte));
        return;
    }
    MBlock *block = POINTER_TO_MBLOCK(self->mblock_info, pointer);
    pthread_mutex_lock(&self->mutex);

    *(void **)pointer = block->root;
    block->root = pointer;
    block->usedCount--;

    if (block->usedCount == 0)
    {
        if (block->previous != NULL)
        {
            block->previous->next = block->next;
        }
        else
        {
            self->blocks[block->index] = block->next;
        }
        if (block->next != NULL)
        {
            block->next->previous = block->previous;
        }
        self->mblock_count[block->index]--;
        if (self->reserve_blocks[block->index] == NULL)
        {
            self->reserve_blocks[block->index] = block;
        }
        else
        {
            free(block);
        }
    }
    pthread_mutex_unlock(&self->mutex);
}

void Pool_Print(Pool *self)
{
    printf("-------------------------------------\n");

    for (u_int i = 0; i < self->mblock_length; i++)
    {
        u_int count = 0;
        u_int usedcount = 0;
        u_int memory = 0;
        MBlock *block = self->blocks[i];
        while (block != NULL)
        {
            usedcount += block->usedCount;
            count += block->count;
            memory += sizeof(MBlock) + block->count * (sizeof(u_byte) + self->mblock_info[block->index][0]) + (sizeof(void *) * (_int)(block->count / MAX_NUMBER_OF_MBLOCK) + 1);
            block = block->next;
        }
        memory = memory;
        printf("Size:[%d byte]\tBlock_Size:[%d]\tAvailability:[%.3lf%%]\tMemory:[%.3lf MB]\n", self->mblock_info[i][0], self->mblock_count[i], count == 0 ? 0 : (double)usedcount / (double)count * 100, memory / 1024.0 / 1024.0);
    }
}